package com.googlecode.cswish.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;

import com.googlecode.cswish.Config;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.ObjectFactory;

@Service
public class FileUtil {
	
	private static final Logger logger = Logger.getLogger(FileUtil.class);
	
	private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
	
	public final static long fileCheckFrequence = 10000;
	
	private Map<String, FileModifyInfo> fileCheckTimes;
	private Map<String, FileModifyInfo> resFileCheckTimes;
	
	@Resource
	private CacheService cacheService;
	
	@PostConstruct
	private void init() {
		fileCheckTimes = cacheService.newCache(FileModifyInfo.class);
		resFileCheckTimes = cacheService.newCache(FileModifyInfo.class);
	}
	
	private class FileModifyInfo {
		File file;
		long lastCheckTime;
		long curModifiedTime;
		
		public FileModifyInfo(File file, long lastCheckTime, long curModifiedTime) {
			this.file = file;
			this.lastCheckTime = lastCheckTime;
			this.curModifiedTime = curModifiedTime;
		}
	}
	
	public static String saveFile(InputStream input, String path, String filename, boolean autoRename) {
		File saveFile;
		String newfilename;
		if (autoRename) {
			newfilename = System.currentTimeMillis() + "." + filename;
			saveFile = new File(path + newfilename);
			// avoid the dupli name
			while (saveFile.exists()) {
				filename = System.currentTimeMillis() + filename;
				saveFile = new File(path + newfilename);
			}
		} else {
			newfilename = filename;
			saveFile = new File(path + newfilename);
		}
		
		try {
			saveFile.getParentFile().mkdirs();
			saveFile.createNewFile();
			byte [] buffer = new byte[DEFAULT_BUFFER_SIZE];
			int len;
			
			FileOutputStream fileOutputStream = new FileOutputStream(saveFile);
			while ((len = input.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != -1) {
				fileOutputStream.write(buffer, 0, len);
			}
			
			fileOutputStream.close();
		} catch (Exception ex) {
			logger.error("saveFile", ex);
			
			return null;
		}
		
		return newfilename;
	}
	
	public static void export(HttpServletResponse response, String filename) {
		int index = filename.lastIndexOf('.');
		String contentType;
		if (index > 0) {
			contentType = filename.substring(index + 1);
		} else {
			contentType = "";
		}
		response.setContentType(contentType);
		int nameIndex = filename.lastIndexOf('/');
		String simpleFileName = filename.substring(nameIndex + 1);
		response.setHeader("Content-Disposition", "attachment; filename=" + simpleFileName);
		int length = (int)new File(filename).length();
		response.setContentLength(length);		
		
		FileInputStream input = null;
		ServletOutputStream output = null;
		try {
			output = response.getOutputStream();
			
			byte [] buffer = new byte[DEFAULT_BUFFER_SIZE];
			int len;
			input = new FileInputStream(filename);
			while ((len = input.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != -1) {
				output.write(buffer, 0, len);
			}
			
			input.close();
			output.flush();
			output.close();
		} catch (Exception ex) {
			logger.error("Failed to export file: " + filename, ex);
		} finally {
			if (input != null) {
				try {
					input.close();
				} catch (IOException ex) {
					logger.error("Failed to close input steam: " + filename, ex);
				}
			}
			if (output != null) {
				try {
					output.close();
				} catch (IOException ex) {
					logger.error("Failed to close output steam: " + filename, ex);
				}
			}
		}
	}
	
	/**
	 * 
	 * @param input
	 * @param encoding
	 * @return
	 * 
	 * @see org.apache.commons.io.FileUtils
	 */
	public static String readFileToString(InputStream input, String encoding) {
		StringWriter sw = new StringWriter();
		
		char[] buffer = new char[DEFAULT_BUFFER_SIZE];
        long count = 0;
        int n = 0;
        try {
	        InputStreamReader in = new InputStreamReader(input, encoding);
	        while (-1 != (n = in.read(buffer))) {
	            sw.write(buffer, 0, n);
	            count += n;
	        }    
			return sw.toString();
        } catch (IOException ex) {
        	logger.error("Read input strean", ex);
        }
        return null;
	}
	
	public static String readWebFileToString(String webPath, String encoding) {
		String webRootPath = ObjectFactory.getInstance().getBean(Config.class, false).getRealWebRootPath();
		
		try {
			return FileUtils.readFileToString(new File(webRootPath + File.separatorChar + webPath), encoding);
		} catch (IOException ex) {
			logger.error("readWebFileToString", ex);
			return null;
		}
	}
	
	/**
	 * Delete file and check the path security
	 * 
	 * @param prefix
	 * @param filename
	 * @return
	 */
	public static boolean delete(String prefix, String filename) {
		File file = new File(filename);
		if (file.getPath().startsWith(prefix)) {
			file.deleteOnExit();
			return true;
		} else {
			return false;
		}
	}
	
	public static void replace(File file, String[] strs, String[] replacements) {
		try {
			File tmpFile = new File(file.getPath() + ".tmp");
			InputStreamReader input = null;
			OutputStreamWriter output = null;
			try {
				input = new InputStreamReader(new FileInputStream(file), "utf8");
				output = new OutputStreamWriter(new FileOutputStream(tmpFile), "utf8");
				BufferedReader reader = new BufferedReader(input);
				String line = reader.readLine();
		        while (line != null) {
		        	for (int i = 0; i < strs.length; i++) {
		        		String str = strs[i];
			        	int index = line.indexOf(str);
			        	if (index >= 0) {
			        		line = line.substring(0, index) + replacements[i] + line.substring(index + str.length());
			        		break;
			        	}
		        	}
		        	IOUtils.write(line, output);
		        	IOUtils.write("\n", output);
		            line = reader.readLine();
		        }
		        reader.close();
			} finally {
				IOUtils.closeQuietly(input);
				IOUtils.closeQuietly(output);
			}
			
			file.delete();
			tmpFile.renameTo(file);
		} catch (IOException ex) {
			logger.error("replace file", ex);
		}
	}
	
	/**
	 * Only copy the top level file
	 * 
	 * @param srcDir
	 * @param destDir
	 */
	public static void copyDirectory(File srcDir, File destDir) throws IOException {
		if (srcDir == null || !srcDir.exists()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Invalid directory: " + (srcDir == null ? " null" : srcDir.getPath()));
			}
			return ;
		}
		
		if (!destDir.exists()) {
			destDir.mkdirs();
		}
		for (File file : srcDir.listFiles()) {
			if (!file.isDirectory()) {
				File destFile = new File(destDir, file.getName());
				FileUtils.copyFile(file, destFile, true);
			}
		}
	}
	
	public static void syncDirectory(File srcDir, File destDir, boolean deepSync, boolean onlyNewFile) throws IOException {
		if (srcDir == null || !srcDir.exists()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Invalid directory: " + (srcDir == null ? " null" : srcDir.getPath()));
			}
			return ;
		}
		
		for (File file : srcDir.listFiles()) {
			File destFile = new File(destDir, file.getName());
			if (file.isDirectory()) {
				if (deepSync) {
					syncDirectory(file, destFile, deepSync, onlyNewFile);
				}
			} else {
				if (!destFile.exists() || !onlyNewFile && destFile.lastModified() < file.lastModified()) {
					FileUtils.copyFile(file, destFile, true);
				}
			}
		}
	}
	
	/**
	 * Check the file is changed
	 * 
	 * TODO: only check in the development model?
	 * 
	 * @param filename
	 * @param isResouceFile
	 * @return
	 */
	public boolean isChanged(String filename, boolean isResouceFile) {
		if (filename == null) {
			return false;
		}
		
		long currentTime = System.currentTimeMillis();
		Map<String, FileModifyInfo> fileCheckTimes = isResouceFile ? this.resFileCheckTimes : this.fileCheckTimes;
		FileModifyInfo fileModifyInfo = fileCheckTimes.get(filename);
		if (fileModifyInfo == null) {
			File file;
			if (isResouceFile) {
				URL url = this.getClass().getResource(filename);
				if (url != null) {
					file = new File(url.getFile());
				} else {
					file = null;
				}
			} else {
				file = new File(filename);
			}
			fileCheckTimes.put(filename, new FileModifyInfo(file, currentTime, currentTime));
			return false;
		}
		
		if (System.currentTimeMillis() - fileModifyInfo.lastCheckTime > fileCheckFrequence) {
			fileModifyInfo.lastCheckTime = currentTime;				// update the check time
			
			// try to find the resource file again
			if (isResouceFile && fileModifyInfo.file == null) {
				URL url = this.getClass().getResource(filename);
				if (url != null) {
					File file = new File(url.getFile());
					fileModifyInfo.file = file;
				}
			}

			// compare the modified time
			long curModified;
			if (fileModifyInfo.file != null) {
				curModified = fileModifyInfo.file.lastModified();
			} else {
				curModified = 0;
			}
			if (curModified > fileModifyInfo.curModifiedTime) {
				fileModifyInfo.curModifiedTime = curModified;		// update the modified time
				return true;
			}
		}
		return false;
	}
}